package cn.itcast.nio.test;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

import static cn.itcast.nio.c1.ByteBufferUtil.debugAll;

@Slf4j
public class MultiThreadServer {
    public static void main(String[] args) throws IOException {
        Thread.currentThread().setName("boss");
        //打开服务器Channel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //设置非阻塞
        ssc.configureBlocking(false);
        //建立boss Selector
        Selector boss = Selector.open();
        //ssc 注册 到 selector
        SelectionKey bossKey = ssc.register(boss, 0, null);
        //设置监控的事件类型
        bossKey.interestOps(SelectionKey.OP_ACCEPT);
        //ssc 绑定端口
        ssc.bind(new InetSocketAddress(8080));
        // 创建固定数量的worker并初始化
        //创建worker selector线程
        Worker[] workers = new Worker[2];
        AtomicInteger index = new AtomicInteger();
        for(int i = 0;i<workers.length;i++){
            workers[i] = new Worker("worker-" + i);
        }
        while (true) {
            boss.select();
            Iterator<SelectionKey> iter = boss.selectedKeys().iterator();
            while(iter.hasNext()){
                SelectionKey key = iter.next();
                iter.remove();
                if(key.isAcceptable()){
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    log.debug("connected...{}",sc.getRemoteAddress());
                    // 关联selector
                    log.debug("before selector...{}",sc.getRemoteAddress());
                    //如果放开下面这句话 这里 after selector... 没有打印 因为 register方法中的selector阻塞
//                    sc.register(worker.selector,SelectionKey.OP_READ,null);
                    //round robin 轮询
                    workers[index.getAndIncrement() % workers.length].register(sc);
                    log.debug("after selector...{}",sc.getRemoteAddress());
                }
            }
        }
    }

    static class Worker{
        private Thread thread;
        private Selector selector;
        private String name;
        private volatile boolean exec = false;
        private ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();

        public Worker(String name) {
            this.name = name;
        }

        //初始化 线程和selector
        public void register(SocketChannel sc) throws IOException {
            if(!exec){
                selector = Selector.open();
                thread = new Thread(()->{
                    while (true) {
                        try {
                            selector.select();
                            Runnable task = queue.poll();
                            if(task!=null){
                                task.run();
                            }
                            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                            while (iter.hasNext()){
                                SelectionKey key = iter.next();
                                iter.remove();
                                if(key.isReadable()){
                                    ByteBuffer buffer = ByteBuffer.allocate(16);
                                    SocketChannel channel = (SocketChannel) key.channel();
                                    log.debug("read...{}",channel.getRemoteAddress());
                                    channel.read(buffer);
                                    buffer.flip();
                                    debugAll(buffer);
                                }
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                },name);
                thread.start();
                exec = true;
            }
            queue.add(()->{
                try {
                    sc.register(selector,SelectionKey.OP_READ,null);
                } catch (ClosedChannelException e) {
                    e.printStackTrace();
                }
            });
            selector.wakeup();
        }
    }

}
